#ifndef STREAMEIGEN_HPP
#define STREAMEIGEN_HPP

#include "globals.hpp"
#include "typeAliases.hpp"
#include <iostream>
#include <utility>
#include <type_traits>

namespace hms {

/**
 * @brief Writes an Eigen matrix or array to a stream.
 * 
 * @tparam Derived Any type derived from Eigen::DenseBase is allowed.
 * @param os
 * @param d The Eigen matrix or array to be written to the stream.
 * @return std::ostream& Now contains a flattened version of the passed in Eigen
 * object.
 */
template<typename Derived>
std::ostream& write( std::ostream& os, const Eigen::DenseBase<Derived>& d );

/**
 * @brief Reads an Eigen matrix or array from a stream.
 * 
 * @tparam Derived Any type derived from Eigen::DenseBase is allowed.
 * @param is
 * @param d Out parameter. Set sizes before passing it in, otherwise it will
 * stay unchanged.
 * @return std::istream& Chainable for possible usage in an overloaded 
 * operator>>.
 */
template<typename Derived>
std::istream& read( std::istream& is, const Eigen::EigenBase<Derived>& d );


template<typename Derived>
using DerivedIfDenseBase = std::enable_if_t<
	std::is_base_of_v<Eigen::DenseBase<Derived>, Derived>,
	Derived
>;

template<typename Derived>
using DerivedIfEigenBase = std::enable_if_t<
	std::is_base_of_v<Eigen::EigenBase<Derived>, Derived>,
	Derived
>;

/**
 * @brief Reads a fixed-size Eigen type (matrix or array) from a stream.
 * @overload
 * 
 * @tparam Derived Any fixed-size type derived from Eigen::DenseBase is allowed.
 * @param is The stream containing the coefficients to be read.
 * @return DerivedIfDenseBase<Derived> SFINAE type so that this function is
 * only used with types derived from Eigen::DenseBase.
 * 
 * For dynamically sized types, use read(std::istream&, Index) or
 * read(std::istream&, Index, Index) instead.
 */
template<typename Derived>
DerivedIfDenseBase<Derived> read(
	std::istream& is
);

/**
 * @brief Reads a vector-like Eigen type from a stream.
 * @overload
 * 
 * @tparam Derived Any type derived from Eigen::DenseBase with one size 
 * parameter set to 1 and the other to Eigen::Dynamic
 * @param is The stream containing the coefficients to be read.
 * @param size The number of rows for a column vector or the number of columns
 * for a row vector.
 * @return DerivedIfDenseBase<Derived> SFINAE type so that this function is
 * only used with types derived from Eigen::DenseBase.
 * 
 * Use this for Eigen::Matrix or Eigen::Array types with either one row or one
 * column. The other size parameter must be Eigen::Dynamic.
 * For fixed size types, use DerivedIfDenseBase<Derived> read(std::istream&)
 * instead. For non-vector dynamically sized types use
 * read(std::istream&, Index, Index) instead.
 */
template<typename Derived>
DerivedIfDenseBase<Derived> read( std::istream& is, Index size );

/**
 * @brief Reads a dynamically sized Eigen matrix or array type from a stream.
 * 
 * @tparam Derived Any type derived from Eigen::DenseBase is allowed. If a fixed
 * type is passed, its size must match the provided size arguments.
 * @param is The stream containing the coefficients to be read.
 * @param rows The number of rows of the matrix or array to be read.
 * @param cols The number of columns of the matrix or array to be read.
 * @return DerivedIfDenseBase<Derived> SFINAE type so that this function is
 * only used with types derived from Eigen::DenseBase.
 */
template<typename Derived>
DerivedIfDenseBase<Derived> read(std::istream& is, Index rows, Index cols);


/**
 * @brief Writes the contents of an Eigen::DenseBase type into a string.
 * Flattens the structure.
 * 
 * @return std::string Contains all coefficients of the passed Eigen::DenseBase
 * object.
 */
template<typename Derived>
std::string toString( const Eigen::DenseBase<Derived>& );

} // end namespace hms

#include "streamEigen.tpp"

#endif